/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.schema;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import edu.ucsd.db.bassarisk.exception.SchemaValidationException;

public class AttributeClass extends DataSequenceClass {
  public static enum AttributeSort {
    Position,
    Run
  }

  private static final Logger LOG = Logger.getLogger(AttributeClass.class.getName());
  private AttributeSort sort;
  private final Set<SchemaAttributeNode> subClasses;

  public AttributeClass(final String name) {
    super(name);
    this.sort = null;
    this.subClasses = new HashSet<SchemaAttributeNode>();
  }

  public AttributeClass(final String name,
                        final Set<? extends SchemaAttributeNode> refinement) {
    super(name);
    this.subClasses = new HashSet<SchemaAttributeNode>();
    this.subClasses.addAll(refinement);
    this.isInitialized = true;
    this.isRefined = true;
    this.fullClassSize = -1;
    try {
      this.computeAttributeSort();
    } catch (SchemaValidationException e) {
      e.printStackTrace();
      LOG.severe(e.getMessage());
    }
    this.refinedClassSize = this.subClasses.size();
  }

  public AttributeSort attributeSort() {
    return this.sort;
  }

  private void computeAttributeSort() throws SchemaValidationException {
    for (SchemaAttributeNode subClass : this.subClasses) {
      boolean isPos = subClass instanceof SchemaPositionAttributeNode;
      boolean isRun = subClass instanceof SchemaRunAttributeNode;
      if (this.sort == null) {
        this.sort = isPos ? AttributeSort.Position : AttributeSort.Run;
      } else {
        if ((this.sort == AttributeSort.Position && isRun)
            || (this.sort == AttributeSort.Run && isPos)) {
          String msg =
              "Illegal attribute name use. Name %s is used for both "
                  + "run-associated and position-associated attributes";
          throw new SchemaValidationException(String.format(msg, this.name));
        }
      }
    }
  }

  @Override
  public Collection<? extends SchemaNode> getRefinement() {
    if (!this.isInitialized) {
      String msg =
          "Refinement not avalible until sequence class has been "
              + "initialized by a shcmea.";
      throw new UnsupportedOperationException(msg);
    }
    return this.subClasses;
  }

  @Override
  public int initailize(final Schema schema) throws SchemaValidationException {
    if (this.name.equals("_COMPUTED_")) { return 0; }
    this.subClasses.clear();
    this.subClasses.addAll(schema.collectAttributeNodesForName(this.name));
    this.isRefined = false;
    this.isInitialized = true;
    this.fullClassSize = this.subClasses.size();
    this.refinedClassSize = this.subClasses.size();
    if (this.refinedClassSize == 0) {
      String msg = String.format("Unknown attribute name @%s.", this.name);
      LOG.severe(msg);
      throw new SchemaValidationException(msg);
    }
    this.computeAttributeSort();
    return this.subClasses.size();
  }

  @Override
  public boolean isPositionSpace() {
    return this.sort == AttributeSort.Position;
  }

  @Override
  public DataSequenceClass
      makeUnion(final List<DataSequenceClass> unionedSequenceClasses) {
    Set<SchemaAttributeNode> unionRefinement = new HashSet<SchemaAttributeNode>();
    for (DataSequenceClass dsc : unionedSequenceClasses) {
      // Combination bindings are only valid over bindings with the same
      // run-class
      assert dsc.getName().equals(this.name);
      assert dsc instanceof AttributeClass;
      unionRefinement.addAll(((AttributeClass) dsc).subClasses);
    }
    return new AttributeClass(this.name, unionRefinement);
  }

  @Override
  protected int refinementSize() {
    return this.subClasses.size();
  }

  @Override
  public String toString() {
    if (this.isInitialized) {
      String format = "AttrClass(@%s, %s of %s)";
      return String.format(format, this.name, this.refinementSize(),
                           this.fullClassSize);
    } else {
      String format = "AttrClass(@%s, uninitialized)";
      return String.format(format, this.name);
    }
  }
}
